/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab-modules.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

//===========================================================================================
#include "Ogre_Gfx.h"
#include <dylab/simobject/SimObject.h>

//===========================================================================================
dylab::Ogre_Gfx::ModelRenderer::ModelRenderer(const String & _name, const String & _materialName, SimObject * _parentSimObject, IScene * _scene)
: mParentSimObject(_parentSimObject), mRenderType(RT_BOUNDARYMESH), mMaterialName(_materialName)
{
    // check arguments
    DYLAB_ASSERT(_scene != NULL);
    DYLAB_ASSERT(mParentSimObject != NULL);

    Ogre::SceneManager * ogreSceneMgr = dynamicCast<Scene>(_scene)->getOgreSceneMgr();

    mOgreManualObject = ogreSceneMgr->createManualObject(_name);
    DYLAB_ASSERT(mOgreManualObject != NULL);

    // attach object to scene node
    ogreSceneMgr->getRootSceneNode()->createChildSceneNode(_name)->attachObject(mOgreManualObject);

    // store our object
    mOgreManualObject->setUserObject(this);

    // set the manual object attributes
    mOgreManualObject->setDynamic(true);
    mOgreManualObject->setQueryFlags(SimObject::RAYQUERY_FLAG);

    // create the object section
    mOgreManualObject->begin(mMaterialName);
    
    // pre-generate the structure
    _update();

    // done
    mOgreManualObject->end();
}
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::ModelRenderer::~ModelRenderer()
{
}
//-----------------------------------------------------------------------------
dylab::String dylab::Ogre_Gfx::ModelRenderer::getName() const
{
    return mOgreManualObject->getName();
}
//-----------------------------------------------------------------------------
dylab::String dylab::Ogre_Gfx::ModelRenderer::getDescription() const
{
    // retrieve the boundary mesh interface
    ComModelStructure::IBoundaryMesh::ScopedShLock mesh(mParentSimObject->getModelStructure());

    // build & return structure gfx description string
    return String::fmt("%1%(v=%2%,i=%3%)", mRenderType, mesh->vertexReader().getCount(), (mesh->faceReader().getCount() * TriangleGeometry3::V_COUNT));
}
//-----------------------------------------------------------------------------
dylab::SimObject * dylab::Ogre_Gfx::ModelRenderer::getSimObject()
{
    return mParentSimObject;
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::setMaterialName(const String & _name)
{
    mMaterialName = _name;
    mOgreManualObject->setMaterialName(0, mMaterialName);
}
//-----------------------------------------------------------------------------
dylab::String dylab::Ogre_Gfx::ModelRenderer::getMaterialName() const
{
    return mMaterialName;
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::setRenderType(render_t _renderType)
{ 
    mRenderType = _renderType; 
}        
//-----------------------------------------------------------------------------
dylab::Ogre_Gfx::ModelRenderer::render_t 
dylab::Ogre_Gfx::ModelRenderer::getRenderType() const
{ 
    return mRenderType; 
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::setCastShadows(bool _castShadows)
{
    mOgreManualObject->setCastShadows(_castShadows);
}
//-----------------------------------------------------------------------------
bool dylab::Ogre_Gfx::ModelRenderer::isCastingShadows() const
{
    return mOgreManualObject->getCastShadows();
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::translate(const Vector3 & _v)
{
    mOgreManualObject->getParentSceneNode()->translate(toOgreVector3(_v));
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::rotate(const Vector3 & _axis, real_t _degrees)
{
    mOgreManualObject->getParentSceneNode()->rotate(toOgreVector3(_axis), Ogre::Degree(_degrees));
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::scale(const Vector3 & _scale)
{
    mOgreManualObject->getParentSceneNode()->scale(toOgreVector3(_scale));
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::prepareForRendering()
{
    // begin generating the object
    mOgreManualObject->beginUpdate(0);

    // update the rendered object
    _update();

    // object generated
    mOgreManualObject->end();
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::_update()
{
    // update mechanism depends on the selected type of rendering 
    switch (mRenderType)
    {
    case RT_BOUNDARYMESH:        
        _updateFromBoundaryMesh();
        break;
    case RT_TETRAHEDRALVOLUME:
        _updateFromTetrahedralVolume();
        break;
    }    
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::_updateFromBoundaryMesh()
{
    {
        // retrieve the boundary mesh interface
        ComModelStructure::IBoundaryMesh::ScopedExLock mesh(mParentSimObject->getModelStructure());

        // prepare for mesh rendering
        mesh->prepareForRendering();
    }

    // retrieve the boundary mesh interface
    ComModelStructure::IBoundaryMesh::ScopedShLock mesh(mParentSimObject->getModelStructure());

    // enter our estimations
    mOgreManualObject->estimateVertexCount(mesh->vertexReader().getCount());
    mOgreManualObject->estimateIndexCount(mesh->faceReader().getCount() * TriangleGeometry3::V_COUNT);

    // object axis aligned bounding box
    Ogre::AxisAlignedBox axisAlignedBox;

    // update the vertices
    for (ComModelStructure::IBoundaryMesh::VertexArray::ConstIterator vertex(mesh->vertexIterator()); vertex.isValid(); vertex++)
    {
        // position
        Ogre::Vector3 pos = toOgreVector3(vertex->position());
        mOgreManualObject->position(pos);
        axisAlignedBox.merge(pos);        

        // normal & texture coordinates
        mOgreManualObject->normal(toOgreVector3(vertex->normal()));
        mOgreManualObject->textureCoord(vertex->getTextureU(), vertex->getTextureV());
    }

    // update the indices
    for (ComModelStructure::IBoundaryMesh::FaceMap::ConstIterator face(mesh->faceIterator()); face.isValid(); face++)    
        for (TriangleGeometry3::VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
            mOgreManualObject->index(face->getVertexIndex(vertexIdx));    

    // overshoot a little
    //TODO: use class attribute
    Ogre::Vector3 v = axisAlignedBox.getMaximum() - axisAlignedBox.getMinimum();
    v.normalise();
    axisAlignedBox.setMinimum(axisAlignedBox.getMinimum() - v);
    axisAlignedBox.setMaximum(axisAlignedBox.getMaximum() + v); 

    // updated object's axis aligned box and the bounding sphere
    mOgreManualObject->setBoundingBox(axisAlignedBox);            
}
//-----------------------------------------------------------------------------
void dylab::Ogre_Gfx::ModelRenderer::_updateFromTetrahedralVolume()
{
    //TODO:
}
//-----------------------------------------------------------------------------